#!/usr/bin/env python
#coding:utf-8

import wx
import wx.grid
import wx.lib.masked as masked
import wxmpl
import numpy as np
import matplotlib
import matplotlib.cm as cm
from pylab import *
from datetime import *

###########################################################################
## Class SPCWindow
###########################################################################

class SPCWindow ( wx.Frame ):
    
    def __init__( self, parent, usl = 10.0, lsl = -10.0, QueryCallBack = None ):
        wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, title = u"SPC分析", pos = wx.DefaultPosition, size = wx.Size( -1,-1 ), style = wx.DEFAULT_FRAME_STYLE|wx.MAXIMIZE|wx.TAB_TRAVERSAL )
        self.QueryCallBack = QueryCallBack
        self.data = None
        self.usl = usl
        self.lsl = lsl
        
        self.SetSizeHintsSz( wx.DefaultSize, wx.DefaultSize )
        
        bSizer15 = wx.BoxSizer( wx.VERTICAL )
        
        self.m_panel12 = wx.Panel( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL )
        bSizer28 = wx.BoxSizer( wx.VERTICAL )
        
        bSizer16 = wx.BoxSizer( wx.HORIZONTAL )
        
        self.m_rbTimeRange = wx.RadioButton( self.m_panel12, wx.ID_ANY, u"时间范围：", wx.DefaultPosition, wx.DefaultSize, 0 )
        bSizer16.Add( self.m_rbTimeRange, 0, wx.ALIGN_CENTER_VERTICAL|wx.TOP|wx.BOTTOM|wx.LEFT, 5 )
        
        self.m_BeginDate = wx.DatePickerCtrl( self.m_panel12, wx.ID_ANY, wx.DefaultDateTime, wx.DefaultPosition, wx.DefaultSize, wx.DP_DROPDOWN )
        bSizer16.Add( self.m_BeginDate, 0, wx.TOP|wx.BOTTOM, 5 )
        
        h = self.m_BeginDate.GetSize().height
        spin2 = wx.SpinButton( self.m_panel12, -1, wx.DefaultPosition, (-1,h), wx.SP_VERTICAL )
        self.m_BeginTime = masked.TimeCtrl(
                        self.m_panel12, -1, name="24 hour control", fmt24hr=True,
                        spinButton = spin2
                        )
        bSizer16.Add( self.m_BeginTime, 0, wx.TOP|wx.BOTTOM, 5)
        bSizer16.Add( spin2, 0, wx.TOP|wx.BOTTOM, 5)
        
        self.m_staticText22 = wx.StaticText( self.m_panel12, wx.ID_ANY, u"-", wx.DefaultPosition, wx.DefaultSize, 0 )
        self.m_staticText22.Wrap( -1 )
        bSizer16.Add( self.m_staticText22, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 )
        
        self.m_endDate = wx.DatePickerCtrl( self.m_panel12, wx.ID_ANY, wx.DefaultDateTime, wx.DefaultPosition, wx.DefaultSize, wx.DP_DROPDOWN )
        bSizer16.Add( self.m_endDate, 0, wx.TOP|wx.BOTTOM|wx.RIGHT, 5 )
        
        spin2 = wx.SpinButton( self.m_panel12, -1, wx.DefaultPosition, (-1,h), wx.SP_VERTICAL )
        self.m_EndTime = masked.TimeCtrl(
                        self.m_panel12, -1, name="24 hour control", fmt24hr=True,
                        spinButton = spin2
                        )
        bSizer16.Add( self.m_EndTime, 0, wx.TOP|wx.BOTTOM, 5)
        bSizer16.Add( spin2, 0, wx.TOP|wx.BOTTOM, 5)
        
        self.m_rbLast = wx.RadioButton( self.m_panel12, wx.ID_ANY, u"最近", wx.DefaultPosition, wx.DefaultSize, 0 )
        self.m_rbLast.SetValue(True)
        bSizer16.Add( self.m_rbLast, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 5 )
        
        self.m_LastCount = wx.SpinCtrl( self.m_panel12, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.Size( 60,-1 ), wx.SP_ARROW_KEYS, 5, 5000, 50 )
        bSizer16.Add( self.m_LastCount, 0, wx.ALL, 5 )
        
        self.m_rbAll = wx.RadioButton( self.m_panel12, wx.ID_ANY, u"全部", wx.DefaultPosition, wx.DefaultSize, 0 )
        bSizer16.Add( self.m_rbAll, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 5 )
        
        self.m_btnQuery = wx.Button( self.m_panel12, wx.ID_ANY, u"查询", wx.DefaultPosition, wx.DefaultSize, 0 )
        bSizer16.Add( self.m_btnQuery, 0, wx.ALL, 5 )
        
        bSizer28.Add( bSizer16, 0, wx.EXPAND, 5 )
        
        bSizer18 = wx.BoxSizer( wx.VERTICAL )
        
        self.m_splitter1 = wx.SplitterWindow( self.m_panel12, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.SP_3D )
        self.m_splitter1.Bind( wx.EVT_IDLE, self.m_splitter1OnIdle )
        
        self.m_panel2 = wx.Panel( self.m_splitter1, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL )
        bSizerLeft = wx.BoxSizer( wx.VERTICAL )
        
        self.m_staImage = wxmpl.PlotPanel(self.m_panel2, wx.ID_ANY)
        bSizerLeft.Add( self.m_staImage, 1, wx.EXPAND |wx.ALL, 5 )
        
        self.m_statistics = wx.ListCtrl( self.m_panel2, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LC_HRULES|wx.LC_NO_HEADER|wx.LC_REPORT|wx.LC_VRULES )
        self.m_statistics.InsertColumn(0, u"Name1")
        self.m_statistics.InsertColumn(1, u"Value1")
        self.m_statistics.InsertColumn(2, u"Null")
        self.m_statistics.InsertColumn(3, u"Name1")
        self.m_statistics.InsertColumn(4, u"Value1")
        self.m_statistics.InsertColumn(5, u"Null")
        self.m_statistics.InsertColumn(6, u"Name1")
        self.m_statistics.InsertColumn(7, u"Value1")
        
        self.m_statistics.InsertStringItem(0, u"总样本数")
        self.m_statistics.SetStringItem(0, 3, u"子组大小")
        self.m_statistics.SetStringItem(0, 4, u"5")
        self.m_statistics.InsertStringItem(1, u"平均值")
        self.m_statistics.SetStringItem(1, 3, u"最大值")
        self.m_statistics.SetStringItem(1, 6, u"最小值")
        self.m_statistics.InsertStringItem(2, u"USL")
        self.m_statistics.SetStringItem(2, 3, u"LSL")
        
        self.m_statistics.InsertStringItem(3, u"")
        
        self.m_statistics.InsertStringItem(4, u"STDEV")
        self.m_statistics.SetStringItem(4, 3, u"CPK")
        self.m_statistics.InsertStringItem(5, u"CP")
        self.m_statistics.SetStringItem(5, 3, u"CPL")
        self.m_statistics.SetStringItem(5, 6, u"CPU")
        self.m_statistics.InsertStringItem(6, u"X平均值")
        self.m_statistics.SetStringItem(6, 3, u"R平均值")
        self.m_statistics.InsertStringItem(7, u"Zusl")
        self.m_statistics.SetStringItem(7, 3, u"Zlsl")
        bSizerLeft.Add( self.m_statistics, 1, wx.ALL|wx.EXPAND, 5 )
        
        self.m_panel2.SetSizer( bSizerLeft )
        self.m_panel2.Layout()
        bSizerLeft.Fit( self.m_panel2 )
        self.m_panel3 = wx.Panel( self.m_splitter1, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL )
        bSizer22 = wx.BoxSizer( wx.VERTICAL )
        
        self.m_splitter3 = wx.SplitterWindow( self.m_panel3, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.SP_3D )
        self.m_splitter3.Bind( wx.EVT_IDLE, self.m_splitter3OnIdle )
        
        self.m_panel14 = wx.Panel( self.m_splitter3, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL )
        bSizer31 = wx.BoxSizer( wx.VERTICAL )
        
        self.m_notebook3 = wx.Notebook( self.m_panel14, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.NB_BOTTOM )
        self.m_runplot = wxmpl.PlotPanel(self.m_notebook3, wx.ID_ANY)
        
        self.m_notebook3.AddPage( self.m_runplot, u"运行图", True )
        self.m_moverplot = wxmpl.PlotPanel(self.m_notebook3, wx.ID_ANY)
        
        self.m_notebook3.AddPage( self.m_moverplot, u"移动极差图", False )
        self.m_xrplot = wxmpl.PlotPanel(self.m_notebook3, wx.ID_ANY)
        
        self.m_notebook3.AddPage( self.m_xrplot, u"均值极差图", False )
        self.m_xstdplot = wxmpl.PlotPanel(self.m_notebook3, wx.ID_ANY)
        
        self.m_notebook3.AddPage( self.m_xstdplot, u"均值标准差", False )
        
        bSizer31.Add( self.m_notebook3, 1, wx.EXPAND |wx.ALL, 5 )
        
        self.m_panel14.SetSizer( bSizer31 )
        self.m_panel14.Layout()
        bSizer31.Fit( self.m_panel14 )
        self.m_panel15 = wx.Panel( self.m_splitter3, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL )
        bSizer32 = wx.BoxSizer( wx.VERTICAL )
        
        self.m_grid3 = wx.grid.Grid( self.m_panel15, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, 0 )
        
        # Grid
        self.m_grid3.CreateGrid( 10, 12 )
        self.gridrowscount = 10
        self.m_grid3.EnableEditing( False )
        self.m_grid3.EnableGridLines( True )
        self.m_grid3.SetGridLineColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_ACTIVECAPTION ) )
        self.m_grid3.EnableDragGridSize( False )
        self.m_grid3.SetMargins( 0, 0 )
        
        # Columns
        self.m_grid3.SetColLabelValue(0, u"测试时间")
        self.m_grid3.SetColSize(0, 150)
        self.m_grid3.SetColLabelValue(1, u"样本1")
        attr = wx.grid.GridCellAttr()
        attr.SetTextColour(wx.Colour(253,58,2))
        self.m_grid3.SetColAttr(1, attr)
        self.m_grid3.SetColLabelValue(2, u"样本2")
        self.m_grid3.SetColAttr(2, attr)
        self.m_grid3.SetColLabelValue(3, u"样本3")
        self.m_grid3.SetColAttr(3, attr)
        self.m_grid3.SetColLabelValue(4, u"样本4")
        self.m_grid3.SetColAttr(4, attr)
        self.m_grid3.SetColLabelValue(5, u"样本5")
        self.m_grid3.SetColAttr(5, attr)
        self.m_grid3.SetColLabelValue(6, u"状态")
        attr = wx.grid.GridCellAttr()
        attr.SetTextColour(wx.Colour(0,255,0))
        attr.SetBackgroundColour(wx.Colour(7,170,248))
        self.m_grid3.SetColAttr(6, attr)
        self.m_grid3.SetColLabelValue(7, u"平均值")
        attr = wx.grid.GridCellAttr()
        attr.SetBackgroundColour(wx.Colour(255,199,199))
        self.m_grid3.SetColAttr(7, attr)
        self.m_grid3.SetColLabelValue(8, u"极差值")
        self.m_grid3.SetColAttr(8, attr)
        self.m_grid3.SetColLabelValue(9, u"标准差")
        self.m_grid3.SetColAttr(9, attr)
        self.m_grid3.SetColLabelValue(10, u"最大值")
        self.m_grid3.SetColAttr(10, attr)
        self.m_grid3.SetColLabelValue(11, u"最小值")
        self.m_grid3.SetColAttr(11, attr)
        self.m_grid3.EnableDragColMove( False )
        self.m_grid3.EnableDragColSize( True )
        self.m_grid3.SetColLabelSize( 25 )
        self.m_grid3.SetColLabelAlignment( wx.ALIGN_CENTRE, wx.ALIGN_CENTRE )
        
        # Rows
        self.m_grid3.AutoSizeRows()
        self.m_grid3.EnableDragRowSize( True )
        self.m_grid3.SetRowLabelSize( 60 )
        self.m_grid3.SetRowLabelAlignment( wx.ALIGN_CENTRE, wx.ALIGN_CENTRE )
        
        # Label Appearance
        
        # Cell Defaults
        self.m_grid3.SetDefaultCellAlignment( wx.ALIGN_CENTER, wx.ALIGN_CENTER )
        bSizer32.Add( self.m_grid3, 1, wx.ALL|wx.EXPAND, 5 )
        
        self.m_panel15.SetSizer( bSizer32 )
        self.m_panel15.Layout()
        bSizer32.Fit( self.m_panel15 )
        self.m_splitter3.SplitHorizontally( self.m_panel14, self.m_panel15, 0 )
        bSizer22.Add( self.m_splitter3, 1, wx.EXPAND, 5 )
        
        self.m_panel3.SetSizer( bSizer22 )
        self.m_panel3.Layout()
        bSizer22.Fit( self.m_panel3 )
        self.m_splitter1.SplitVertically( self.m_panel2, self.m_panel3, 0 )
        bSizer18.Add( self.m_splitter1, 1, wx.EXPAND, 5 )
        
        bSizer28.Add( bSizer18, 1, wx.EXPAND, 5 )
        
        self.m_panel12.SetSizer( bSizer28 )
        self.m_panel12.Layout()
        bSizer28.Fit( self.m_panel12 )
        bSizer15.Add( self.m_panel12, 1, wx.EXPAND, 5 )
        
        self.SetSizer( bSizer15 )
        self.Layout()
        
        self.Centre( wx.BOTH )
        self.Maximize()
        
        size = self.GetSize()
        self.m_splitter1.SetSashPosition(size[0]/2)
        self.m_splitter3.SetSashPosition(size[1]/2)
        
        mpl.rcParams['font.sans-serif'] = ['SimHei']
        mpl.rcParams['axes.unicode_minus'] = False
        
        self.m_btnQuery.Bind(wx.EVT_BUTTON, self.OnQuery)

        self.OnQuery(None)
    
    def m_splitter1OnIdle( self, event ):
        self.m_splitter1.SetSashPosition( 0 )
        self.m_splitter1.Unbind( wx.EVT_IDLE )
    
    def m_splitter3OnIdle( self, event ):
        self.m_splitter3.SetSashPosition( 0 )
        self.m_splitter3.Unbind( wx.EVT_IDLE )
        
    def OnQuery(self, evt):
        wx.BeginBusyCursor()
        if self.QueryCallBack:
            if self.m_rbTimeRange.GetValue():
                begindate = self.m_BeginDate.GetValue()
                begintime = self.m_BeginTime.GetValue(True)
                enddate = self.m_endDate.GetValue()
                endtime = self.m_EndTime.GetValue(True)
                begin = datetime(begindate.GetYear(), begindate.GetMonth(), begindate.GetDay(),
                                 begintime.GetHour(), begintime.GetMinute(), begintime.GetSecond())
                end = datetime(enddate.GetYear(), enddate.GetMonth(), enddate.GetDay(),
                                 endtime.GetHour(), endtime.GetMinute(), endtime.GetSecond())
                self.data = self.QueryCallBack(1, begin, end)
            elif self.m_rbLast.GetValue():
                self.data = self.QueryCallBack(2, self.m_LastCount.GetValue())
            elif self.m_rbAll.GetValue():
                self.data = self.QueryCallBack(3)
        self.UpdateView()
        wx.EndBusyCursor()
                
    def UpdateView(self):
        #if not self.data:
        '''mu, sigma = 50, 15
        self.data = [ [datetime.now(), v] for v in mu + sigma*randn(200)]'''
        
        if not self.data or len(self.data) < 20:
            wx.MessageBox(u"数据库中测量结果小于20个，不能进行SPC分析")
            return
        
        size = len(self.data)/5*5
        self.data = self.data[:size]
        self.Freeze()
        self.UpdateGrid()
        self.UpdateList()
        self.DrawStatImage()
        self.DrawXRImage()
        self.Thaw()
        
    def UpdateGrid(self):
        dat = np.array(self.data)
        dat.shape = -1, 5, 2
        self.m_grid3.DeleteRows(0, self.gridrowscount, False)
        self.gridrowscount = 0
        for i, s in enumerate(dat):
            if len(s) == 5:
                self.m_grid3.AppendRows()
                self.gridrowscount += 1
                self.m_grid3.SetCellValue(i, 0, s[0][0].strftime("%Y-%m-%d %H:%M:%S"))
                for j, v in enumerate(s):
                    self.m_grid3.SetCellValue(i, j+1, '%.5f' % v[1])
                d = s[:,1]
                mean = np.mean(d)
                min = np.min(d)
                max = np.max(d)
                self.m_grid3.SetCellValue(i, 7, '%.5f' % mean)
                self.m_grid3.SetCellValue(i, 8, '%.5f' % (max-min))
                self.m_grid3.SetCellValue(i, 9, '%.5f' % np.std(d))
                self.m_grid3.SetCellValue(i, 10, '%.5f' % max)
                self.m_grid3.SetCellValue(i, 11, '%.5f' % min)
                
    def UpdateList(self):
        if not self.data or len(self.data) < 1:
            return
        dat = np.array(self.data)
        self.m_statistics.SetStringItem(0, 1, '%d' % len(self.data))
        d = dat[:,1]
        self.m_statistics.SetStringItem(1, 1, '%.5f' % np.mean(d))
        self.m_statistics.SetStringItem(1, 4, '%.5f' % np.max(d))
        self.m_statistics.SetStringItem(1, 7, '%.5f' % np.min(d))
        self.m_statistics.SetStringItem(2, 1, '%.5f' % self.usl)
        self.m_statistics.SetStringItem(2, 4, '%.5f' % self.lsl)
        self.m_statistics.SetStringItem(4, 1, '%.5f' % np.std(d))
        gd = d.reshape(-1,5)
        xs = np.mean(gd, axis = 1)
        xm = np.mean(xs)
        self.m_statistics.SetStringItem(6, 1, '%.5f' % xm)
        rs = np.max(gd, axis = 1) - np.min(gd, axis = 1)
        rm = np.mean(rs)
        self.m_statistics.SetStringItem(6, 4, '%.5f' % rm)
        p = rm/2.33
        try:
            zusl = (self.usl - xm)/p
        except:
            zusl = 0.0
        try:
            zlsl = (xm - self.lsl)/p
        except:
            zlsl = 0.0
        self.m_statistics.SetStringItem(7, 1, '%.5f' % zusl)
        self.m_statistics.SetStringItem(7, 4, '%.5f' % zlsl)
        
        self.m_statistics.SetStringItem(4, 4, '%.5f' % ((zusl < zlsl and zusl or zlsl)/3.0))
        try:
            self.m_statistics.SetStringItem(5, 1, '%.5f' % ((self.usl-self.lsl)/6.0/p))
        except:
            self.m_statistics.SetStringItem(5, 1, '0.0000')
        self.m_statistics.SetStringItem(5, 4, '%.5f' % (zlsl/3.0))
        self.m_statistics.SetStringItem(5, 7, '%.5f' % (zusl/3.0))
        
    def DrawStatImage(self):
        if not self.data or len(self.data) < 1:
            return
        dat = np.array(self.data)
        x = dat[:,1]
        
        fig = self.m_staImage.get_figure()
        axes = fig.gca()
        axes.clear()
        # the histogram of the data
        n, bins, patches = axes.hist(x, 100, normed=True)
    
        # add a 'best fit' line
        y = normpdf( bins, 50, 15)
        l = axes.plot(bins, y, 'r--', linewidth=2)
    
        axes.set_xlim((0, 160))
        axes.set_xlabel(u'值')
        axes.set_ylabel(u'概率')
        self.m_staImage.draw()
        
    def DrawXRImage(self):
        if not self.data or len(self.data) < 1:
            return
        dat = np.array(self.data)
        v = dat[:,1]
        gd = v.reshape(-1,5)
        xs = np.mean(gd, axis = 1)
        rs = np.max(gd, axis = 1) - np.min(gd, axis = 1)
        stds = [np.std(vals) for vals in gd]
        
        #-----------------------------------------------------------
        fig = self.m_runplot.get_figure()
        axes = fig.gca()
        axes.clear()
        axes.plot(v, '-o', ms=5, lw=2, alpha=0.7, mfc='orange')
        axes.grid(True)
        self.m_runplot.draw()
        
        #-----------------------------------------------------------
        fig = self.m_moverplot.get_figure()
        a1 = fig.add_subplot(2, 1, 1)
        a1.clear()
        a1.plot(v, '-o', ms=5, lw=2, alpha=0.7, mfc='green')
        a1.grid(True)
        
        mr = []
        for i in range(1, len(v)):
            mr.append(v[i] - v[i-1])
        a2 = fig.add_subplot(2, 1, 2)
        a2.clear()
        a2.plot(mr, '-o', ms=5, lw=2, alpha=0.7, mfc='green')
        a2.grid(True)
        self.m_moverplot.draw()
        
        #-----------------------------------------------------------
        fig = self.m_xrplot.get_figure()
        a1 = fig.add_subplot(2, 1, 1)
        a1.clear()
        a1.plot(xs, '-o', ms=5, lw=2, alpha=0.7, mfc='green')
        a1.grid(True)
        
        a2 = fig.add_subplot(2, 1, 2)
        a2.clear()
        a2.plot(rs, '-o', ms=5, lw=2, alpha=0.7, mfc='green')
        a2.grid(True)
        self.m_xrplot.draw()
        
        #-----------------------------------------------------------
        fig = self.m_xstdplot.get_figure()
        a1 = fig.add_subplot(2, 1, 1)
        a1.clear()
        a1.plot(xs, '-o', ms=5, lw=2, alpha=0.7, mfc='green')
        a1.grid(True)
        
        a2 = fig.add_subplot(2, 1, 2)
        a2.clear()
        a2.plot(stds, '-o', ms=5, lw=2, alpha=0.7, mfc='green')
        a2.grid(True)
        self.m_xstdplot.draw()
